package com.beemans.topon.ads

import android.os.Handler
import android.os.Looper
import android.widget.FrameLayout
import androidx.annotation.Keep
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.anythink.core.api.ATAdConst
import com.anythink.core.api.ATAdInfo
import com.anythink.core.api.ATMediationRequestInfo
import com.anythink.core.api.AdError
import com.anythink.splashad.api.ATSplashAd
import com.anythink.splashad.api.ATSplashAdExtraInfo
import com.anythink.splashad.api.ATSplashAdListener
import com.beemans.topon.kts.context
import com.beemans.topon.kts.deepClone
import com.beemans.topon.kts.lifecycleOwnerEx
import com.beemans.topon.log.Logger
import kotlinx.coroutines.Job
import kotlinx.parcelize.RawValue
import java.util.concurrent.ConcurrentHashMap

/**
 * @author ti
 * @date 2022/10/26.
 */
object TpSplashAd {
    private val logTag by lazy { this::class.java.simpleName }
    private val nextAdLoaderMap by lazy { ConcurrentHashMap<String, MutableList<TpSplashAdLoader>>() }
    private val allAdLoaderMap by lazy { ConcurrentHashMap<String, MutableList<TpSplashAdLoader>>() }

    /**
     * 开始加载广告
     *
     * @param owner 传入 Activity 或 Fragment
     * @param config 广告配置
     * @param flContainer 广告视图容器
     * @param splashAdCallback 广告回调
     */
    fun start(
        owner: LifecycleOwner,
        config: TpSplashAdConfig,
        flContainer: FrameLayout? = null,
        splashAdCallback: (TpSplashAdCallback.() -> Unit)? = null
    ) {
        val loader = TpSplashAdLoader(owner, config, flContainer, splashAdCallback)
        start(loader)
    }

    /**
     * 开始加载广告
     *
     * @param loader 广告加载器
     */
    fun start(loader: TpSplashAdLoader) {
        if (!loader.config.isAllowFullAdCover) {
            //防止全屏广告多次加载导致叠加展示
            var isFullAdLoading = loader.isAdRequesting() || loader.isAdShowing()
            if (!isFullAdLoading) {
                isFullAdLoading = !loader.config.isOnlyPreload
                        && (isAdLoading(loader) || TpInsertAd.isAdLoading() || TpRewardAd.isAdLoading())
            }
            if (isFullAdLoading) {
                Logger.logI(logTag, "onAdStartFailed")
                loader.callback?.onAdStartFailed?.invoke()
                return
            }
        }
        addLoader(loader, nextAdLoaderMap)
        val allLoaders = addLoader(loader, allAdLoaderMap)
        //当前有其他相同广告位正在加载广告时，等待加载
        if (allLoaders.any { it.isAdRequesting() }) {
            loader.startAdTimeOutHandle()
            return
        }
        loader.start()
    }

    /**
     * 是否正在加载广告（广告请求 + 广告展示）
     */
    fun isAdLoading(): Boolean {
        return isAdLoading(null)
    }

    /**
     * 是否正在展示广告
     */
    fun isAdShowing(): Boolean {
        return isAdShowing(null)
    }

    private fun isAdLoading(loader: TpSplashAdLoader?): Boolean {
        return allAdLoaderMap.values.any { list ->
            list.any { it != loader && !it.config.isOnlyPreload }
        }
    }

    private fun isAdShowing(loader: TpSplashAdLoader?): Boolean {
        return allAdLoaderMap.values.any { list ->
            list.any { it != loader && it.isAdShowing() }
        }
    }

    /**
     * 开始加载列表中最近的一个
     */
    private fun startNext(oldLoader: TpSplashAdLoader) {
        val placementId = oldLoader.config.placementId
        val loaders = nextAdLoaderMap[placementId]
        loaders?.lastOrNull()?.let { start(it) }
    }

    /**
     * 添加广告加载器
     */
    private fun addLoader(
        loader: TpSplashAdLoader,
        loaderMap: ConcurrentHashMap<String, MutableList<TpSplashAdLoader>>
    ): List<TpSplashAdLoader> {
        val placementId = loader.config.placementId
        val loaders = loaderMap.getOrPut(placementId) { mutableListOf() }
        if (!loaders.contains(loader)) {
            loaders.add(loader)
            loaderMap[placementId] = loaders
        }
        return loaders
    }

    /**
     * 从列表中移除对应的加载
     */
    private fun removeLoader(
        loader: TpSplashAdLoader,
        loaderMap: ConcurrentHashMap<String, MutableList<TpSplashAdLoader>>
    ) {
        val placementId = loader.config.placementId
        val loaders = loaderMap[placementId]
        if (loaders.isNullOrEmpty()) return
        loaders.remove(loader)
        loaderMap[placementId] = loaders
    }

    /**
     * 广告加载器
     *
     * @param owner 传入 Activity 或 Fragment
     * @param config 广告配置
     * @param flContainer 广告视图容器
     * @param splashAdCallback 广告回调
     */
    class TpSplashAdLoader(
        private val owner: LifecycleOwner,
        val config: TpSplashAdConfig,
        private val flContainer: FrameLayout? = null,
        private val splashAdCallback: (TpSplashAdCallback.() -> Unit)? = null
    ) : DefaultLifecycleObserver, ATSplashAdListener {
        private val context by lazy { owner.context }
        private val adWidth by lazy { config.adViewWidth }
        private val adHeight by lazy { config.adViewHeight }
        private val placementId by lazy { config.placementId }
        private val timeout by lazy { config.timeout }
        private val adsHandler by lazy { Handler(Looper.getMainLooper()) }

        private var splashAdLayout: FrameLayout? = flContainer
        private var atSplashAd: ATSplashAd? = null
        private var renderJob: Job? = null
        private var tpAdStatus: TpSplashAdStatus? = null

        var callback = splashAdCallback?.let { TpSplashAdCallback().apply(it) }
            private set

        init {
            init()
        }

        private fun init() {
            owner.lifecycleOwnerEx.lifecycle.addObserver(this)
        }

        override fun onAdLoaded(isTimeout: Boolean) {
            if (isTimeout) return
            onAdLoaded()
        }

        override fun onAdLoadTimeout() {
            onAdTimeOutCallback()
        }

        override fun onNoAdError(error: AdError?) {
            onAdLoadFailed(error, false)
        }

        override fun onAdShow(atAdInfo: ATAdInfo?) {
            Logger.logI(logTag, "onAdShow --- atAdInfo:$atAdInfo")
            callback?.onAdShow?.invoke(atAdInfo)

            if (config.isAutoLoadAd && atSplashAd?.isAdReady != true) {
                Logger.logI(logTag, "onAdShow --- autoLoadAd")
                val newConfig = config.deepClone()?.apply { isOnlyPreload = true }
                newConfig?.let { start(owner, it) }
            }
        }

        override fun onAdClick(atAdInfo: ATAdInfo?) {
            Logger.logI(logTag, "onAdClick --- atAdInfo:$atAdInfo")
            callback?.onAdClick?.invoke(atAdInfo)
        }

        override fun onAdDismiss(atAdInfo: ATAdInfo?, extraInfo: ATSplashAdExtraInfo?) {
            Logger.logI(logTag, "onAdClose --- atAdInfo:$atAdInfo")
            //不展示点睛功能时调用，释放资源
            extraInfo?.atSplashEyeAd?.destroy()

            callback?.onAdClose?.invoke(atAdInfo)
            onAdRelease()
        }

        /**
         * 是否正在请求广告
         */
        fun isAdRequesting(): Boolean {
            return tpAdStatus?.isRequesting == true
        }

        /**
         * 是否正在展示广告
         */
        fun isAdShowing(): Boolean {
            return tpAdStatus?.isShowing == true
        }

        /**
         * 获取广告加载器
         */
        private fun getAtSplashAd(): ATSplashAd {
            return when {
                config.sourceConfig.isNotBlank() -> {
                    ATSplashAd(
                        context,
                        placementId,
                        this,
                        timeout.toInt(),
                        config.sourceConfig
                    )
                }
                else -> {
                    @Suppress("DEPRECATION")
                    ATSplashAd(
                        context,
                        placementId,
                        config.atRequestInfo,
                        this,
                        timeout.toInt()
                    )
                }
            }.apply {
                setLocalExtra(mutableMapOf<String, Any>().apply {
                    if (adWidth > 0 && adHeight > 0) {
                        put(ATAdConst.KEY.AD_WIDTH, adWidth)
                        put(ATAdConst.KEY.AD_HEIGHT, adHeight)
                    }
                })
            }
        }

        /**
         * 开始加载广告
         */
        internal fun start() {
            if (atSplashAd == null) {
                atSplashAd = getAtSplashAd()
            }
            if (splashAdLayout == null) {
                splashAdLayout = flContainer
            }
            if (callback == null) {
                callback = splashAdCallback?.let { TpSplashAdCallback().apply(it) }
            }
            onAdStartCallback()
            startAdTimeOutHandle()

            if (atSplashAd?.isAdReady == true) {
                onAdLoaded()
                return
            }
            tpAdStatus?.isRequesting = true
            atSplashAd?.loadAd()
        }

        /**
         * 执行广告加载超时处理
         */
        fun startAdTimeOutHandle() {
            if (timeout <= 0) return
            adsHandler.postDelayed({
                onAdStartCallback()
                onAdTimeOutCallback()
            }, timeout)
        }

        /**
         * 广告开始加载回调
         */
        private fun onAdStartCallback() {
            if (tpAdStatus == null) {
                tpAdStatus = TpSplashAdStatus(placementId)
            }
            if (tpAdStatus?.isStarted == false) {
                tpAdStatus?.isStarted = true
                Logger.logI(logTag, "onAdStart")
                callback?.onAdStart?.invoke()
            }
        }

        /**
         * 广告加载超时
         */
        private fun onAdTimeOutCallback() {
            onAdLoadFailed(null, true)
        }

        /**
         * 展示广告
         */
        private fun showAd() {
            if (!config.isAllowFullAdCover) {
                val isFullAdLoading = isAdShowing(this)
                if (isFullAdLoading) {
                    onAdShowFailed()
                    return
                }
            }
            tpAdStatus?.isShowing = true

            var isShowAd = false
            renderJob?.cancel()
            owner.lifecycleScope.launchWhenResumed {
                isShowAd = true
                atSplashAd?.show(context, splashAdLayout)
            }.also {
                renderJob = it
            }.invokeOnCompletion {
                if (isShowAd || renderJob?.isCancelled == true) return@invokeOnCompletion
                onAdShowFailed()
            }
        }

        /**
         * 广告展示失败
         */
        private fun onAdShowFailed() {
            Logger.logI(logTag, "onAdShowFailed")
            tpAdStatus?.isShowing = false
            callback?.onAdShowFailed?.invoke()

            onAdRelease()
        }

        /**
         * 广告加载成功
         */
        private fun onAdLoaded() {
            onAdLoadComplete {
                Logger.logI(logTag, "onAdLoaded")
                callback?.onAdLoaded?.invoke()

                //进行广告展示
                if (tpAdStatus != null && !config.isOnlyPreload && splashAdLayout != null) {
                    showAd()
                }
                startNext(this)
            }
        }

        /**
         * 广告加载失败
         */
        private fun onAdLoadFailed(adError: AdError?, isTimeout: Boolean) {
            onAdLoadComplete {
                Logger.logI(logTag, "onAdLoadFailed --- isTimeout:$isTimeout   adError:$adError")
                callback?.onAdLoadFailed?.invoke(adError)
                onAdRelease()
                startNext(this)
            }
        }

        /**
         * 广告加载完成
         */
        private fun onAdLoadComplete(callback: () -> Unit) {
            tpAdStatus?.isRequesting = false
            adsHandler.removeCallbacksAndMessages(null)

            if (tpAdStatus?.isLoadCompleted == false) {
                tpAdStatus?.isLoadCompleted = true
                removeLoader(this, nextAdLoaderMap)
                if (config.isOnlyPreload) {
                    removeLoader(this, allAdLoaderMap)
                }
                callback.invoke()
            }
        }

        /**
         * 获取缓存信息
         */
        fun checkValidAdCaches(): List<ATAdInfo> {
            val atSplashAd = this.atSplashAd ?: getAtSplashAd()
            return atSplashAd.checkValidAdCaches() ?: listOf()
        }

        /**
         * 广告释放
         */
        fun onAdRelease() {
            adsHandler.removeCallbacksAndMessages(null)
            removeLoader(this, nextAdLoaderMap)
            removeLoader(this, allAdLoaderMap)
            splashAdLayout?.removeAllViews()
            splashAdLayout = null
            tpAdStatus = null
            callback = null
        }

        override fun onDestroy(owner: LifecycleOwner) {
            owner.lifecycle.removeObserver(this)
            atSplashAd = null
            onAdRelease()
        }
    }

    @Keep
    data class TpSplashAdStatus(
        val placementId: String,                //广告位id
        var isStarted: Boolean = false,         //广告是否已开始加载
        var isRequesting: Boolean = false,      //广告正在请求中
        var isShowing: Boolean = false,         //广告正在展示中
        var isLoadCompleted: Boolean = false,   //广告已加载完成（加载成功 or 加载失败）
    )

    @Keep
    data class TpSplashAdConfig(
        var placementId: String,                    //广告位id
        var adViewWidth: Int = 0,                   //广告视图宽度，大于0生效
        var adViewHeight: Int = 0,                  //广告视图高度，大于0生效
        var timeout: Long = 5000,                   //加载超时时间，大于0生效
        var isOnlyPreload: Boolean = false,         //是否仅预加载，不立即展示广告
        var sourceConfig: String = "",              //兜底开屏配置信息，只用于解决应用安装后的首次开屏加载超时
        var isAutoLoadAd: Boolean = true,           //是否自动预加载广告
        var isAllowFullAdCover: Boolean = false,    //是否允许全屏广告（开屏、插屏、激励视频）叠加展示
        @Deprecated("Use sourceConfig", replaceWith = ReplaceWith("sourceConfig"))
        var atRequestInfo: @RawValue ATMediationRequestInfo? = null,    //各个平台的参数信息，兜底开屏配置
    )

    class TpSplashAdCallback {
        var onAdStart: (() -> Unit)? = null
        var onAdStartFailed: (() -> Unit)? = null
        var onAdLoaded: (() -> Unit)? = null
        var onAdLoadFailed: ((adError: AdError?) -> Unit)? = null
        var onAdShow: ((atAdInfo: ATAdInfo?) -> Unit)? = null
        var onAdShowFailed: (() -> Unit)? = null
        var onAdClick: ((atAdInfo: ATAdInfo?) -> Unit)? = null
        var onAdClose: ((atAdInfo: ATAdInfo?) -> Unit)? = null

        fun onAdStart(onAdStart: () -> Unit) {
            this.onAdStart = onAdStart
        }

        fun onAdStartFailed(onAdStartFailed: () -> Unit) {
            this.onAdStartFailed = onAdStartFailed
        }

        fun onAdLoaded(onAdLoaded: () -> Unit) {
            this.onAdLoaded = onAdLoaded
        }

        fun onAdLoadFailed(onAdLoadFailed: (adError: AdError?) -> Unit) {
            this.onAdLoadFailed = onAdLoadFailed
        }

        fun onAdShow(onAdShow: (atAdInfo: ATAdInfo?) -> Unit) {
            this.onAdShow = onAdShow
        }

        fun onAdShowFailed(onAdShowFailed: () -> Unit) {
            this.onAdShowFailed = onAdShowFailed
        }

        fun onAdClick(onAdClick: (atAdInfo: ATAdInfo?) -> Unit) {
            this.onAdClick = onAdClick
        }

        fun onAdClose(onAdClose: (atAdInfo: ATAdInfo?) -> Unit) {
            this.onAdClose = onAdClose
        }
    }
}